﻿//#region Common
// File: CsoGateway.Collections.Hashing.js
// Version: 0.7.1.0
// Author: Pascal Dufresne
// Date: 2008-08-05
// Last update: 2009-05-25
// http://csogateway.codeplex.com
// http://csogateway.metaobjects.ca
// Copyright (C) 2010 Pascal Dufresne


/*
 * Register CsoGateway.Collections namespace. Import CsoGateway.System namespace.
 */
Type.registerNamespace("CsoGateway.Collections");
ImportNamespace(CsoGateway.System);
//#endregion Common

//#region Hashing
/*
 * CsoGateway.Collections.Hashing is a static class. It contains static utility methods that calculates hash codes. Methods
 * in this class are not in the prototype. They can be called without creating an object with the new operator.
 *
 * Methods                          Description
 * ----------                       -----------------------------------------------------------
 * Hash(Object)                     Return a non-negative Number which is a hash code for the given
 *                                  object. The strategy used to calculated the hash code
 *                                  varies according the the actual Type of the object.
 *
 * HashFromString(String)           Uses and adaptation of Bob Jenkins 'One-at-a-Time' Hash function
 *                                  to compute a hash for the given String.
 */



/*
 * Creates a new CsoGateway.Collections.Hashing. All methods of the Hashing class are static so this
 * constructor should never be called. Exists only for structural integrity of the Hashing class.
 */

CsoGateway.Collections.Hashing = function Hashing()
{
	throw new Error('CsoGateway.Collections.Hashing is a static class. Its constructor should not be called.');
}

/* 
 * Return a non-negative Number which is a hash code for the given object.
 *
 * For most types, the stategy used is to convert the object into a string and using the
 * HashFromString(String) function.
 *
 * -Calculation of hashes by method Hash(Object):
 *
 * -Hashes for objects of type String, Number, Boolean, Function will always return the same
 *  value because these objects are immutable or because their hash is created from a property that cannot
 *  change.	
 *
 * -Hashes for objects of type Date are calculated based on the current value of the object. If the value
 *	changes, the hash will be different even if it is the same object.
 *
 * -Hashes for objects of type Array are calculated, the first time, to be
 *		Hash(a) ^ Hash(z)
 *	where a is the first element of the Array and z is the last. So 2 distinct arrays with the same first
 *	and last elements will return the same hash code. Once the hash is calculated for an Array, the value
 *	is stored in the object itself and future call to the Hash function for this object will return this
 *	stored value. So changing the content of the Array will not change the value returned by the Hash
 *	function.
 *
 * -There is no good way to compute a hash for an arbitrary object of type Object since the name of the
 *	custom properties of the object are not known. Hashes for objects of type Object are calculated, the
 *	first time, to be the bitwise XOR of the results of the calls to this same Hash function for the first
 *	3 properties in the object as returned by the statement for..in on this object.
 *	Once the hash is calculated for an object of type Object, the value is stored in the object itself
 *	and future call to the Hash function on this object will return this stored value.
 *	So adding/removing or changing the value of the properties of the object will not change its hash.
 *
 * -Objects that are not of type String, Number, Boolean, Function, Date, Array or Object
 *	can still be passed to this method if they implement the ICsoHashProvider interface. In this case,
 *	hashes will be calculated to be the bitwise XOR of the results of the Hash function called on each
 *	of the objects returned by the ICsoHashProvider.getCsoHashSource() method.
 *	
 *  If the object is not of type String, Number, Boolean, Function, Date, Array, Object
 *	and does not implement ICsoHashProvider, the hash will be calculated as if the object was of type
 *	Object.
 *
 */
 
 
CsoGateway.Collections.Hashing.Hash = function(obj)
{
	AssertArgumentNotNullOrUndefined(obj, 'obj in CsoGateway.Collections.Hashing.Hash');

	with(CsoGateway.Collections.Hashing)
	{
		var hashCode = null;
		var varCount;

		if(obj._csoHashCode != undefined)
		{
			hashCode = obj._csoHashCode;
		}
		else
		{
			switch(Object.getType(obj))
			{
				case String:
					hashCode = HashFromString(obj)
					break;
				case Number:
					hashCode = HashFromString('Number:' + obj.toString());
					break;
				case Object:
					hashCode = HashFromString('Object:' + obj.toString());
					varCount = 0;
					for(var v in obj)
					{
						hashCode ^= Hash(obj[v]);
						if(++varCount == 3)
							break;	
					}
					obj._csoHashCode = hashCode; //Complex hash. Store for better performance
					break;
				case Boolean:
					hashCode = HashFromString((obj ? 'true' : 'false'));
					break;
				case Array:
					hashCode = ((obj.length > 0)?(this.Hash(obj[0]) ^ this.Hash(obj[obj.length-1])):HashFromString('Array'));
					obj._csoHashCode = hashCode; //Complex hash. Store for better performance
					break;
				case Date:
					hashCode = this.HashFromString(obj.valueOf().toString());
					break;
				case Function:
					hashCode = this.HashFromString('Function:' + obj.name);
					break;
									
				default:
					if(CsoGateway.System.CsoGenerated.isInstanceOfType(obj) &&
						CsoGateway.Collections.ICsoHashProvider.isImplementedBy(obj))
					{
						sources = obj.getCsoHashSource();
						hashCode = 0;
						for(var s in sources)
						{
							hashCode ^= Hash(sources[s]);
						}
						obj._csoHashCode = hashCode;
					}
					else
					{
						hashCode = HashFromString(obj.toString());
						varCount = 0;
						for(var v in obj)
						{
							hashCode ^= Hash(obj[v]);
							if(++varCount == 3)
								break;	
						}
						obj._csoHashCode = hashCode; //Complex hash. Store for better performance
					}
			}
		}
		
		return hashCode;
	}
}


/*
 * Uses an adaptation of the Bob Jenkins 'One-at-a-Time' Hash function to calculate a hash for a string.
 * 
 * http://www.burtleburtle.net/bob/hash/doobs.html
 *
 */

CsoGateway.Collections.Hashing.HashFromString = function(stringSource)
{
	AssertArgumentType(stringSource, String, 'stringSource in CsoGateway.Collections.Hashing.HashFromString');
	
	var hashed = 0;
	 
	for (var i = 0; i < stringSource.length; i++)
	{
		hashed += stringSource.charCodeAt(i);
		hashed += (hashed << 20);
		hashed ^= (hashed >> 12);
	}
	
	hashed += (hashed << 6);
	hashed ^= (hashed >> 22);
	hashed += (hashed << 30);
    
	return Math.abs(hashed);
}

CsoGateway.Collections.Hashing.registerClass('CsoGateway.Collections.Hashing', CsoNative, Sys.IDisposable);

/*
 * End of CsoGateway.Collections.Hashing definition
 */
//#endregion Hashing




//#region ICsoHashProvider
/*
 * CsoGateway.Collections.ICsoHashProvider defines an interface to expose a custom hashing source. When
 * a class implements this interface, the method CsoGateway.Collections.Hashing.Hash will use the objects
 * returned by the method getCsoHashSource() to calculated the hash code.
 *
 */

CsoGateway.Collections.ICsoHashProvider = function ICsoHashProvider()
{
	throw new Error('CsoGateway.Collections.ICsoHashProvider is an interface.');
};

CsoGateway.Collections.ICsoHashProvider.getCsoHashSource = function(){};

CsoGateway.Collections.ICsoHashProvider.registerInterface("CsoGateway.Collections.ICsoHashProvider");

/*
* End of CsoGateway.Collections.ICsoHashProvider definition
*/

//#endregion ICsoHashProvider